package org.pragmindz.facelets.blockinput;
/*
    A custom facelets component that simulates block/cell input like the ones used on wire transfer or tax forms.
    Copyright (C) 2009 PragMindZ
    http://www.pragmindz.org
    mailto://info@pragmindz.org

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import java.io.*;
import java.util.ArrayList;
import java.util.List;


/**
 This component represents cell based input like the ones you find on tax forms and alike.
 */
public class BlockInputComponent extends UIInput
{

    public static final String COMPONENT_TYPE = "org.pragmindz.facelets.BlockInputComponent";

    private int size;
    private String mask;
    private String filter = "/*/";
    private String callback;
    private String cellCallback;

    private List<String> clientIds = new ArrayList<String>();

    private Object[] values;

    public void encodeBegin(FacesContext context) throws IOException
    {
        final ResponseWriter writer = context.getResponseWriter();

        String lClientId = "";
        final Converter lConverter = getConverter();
        String lValue;
        if (lConverter != null)
            lValue =  lConverter.getAsString(context, this, getValue());
        else
            lValue = (String) getValue();

        //Ignore size attribute if mask is specified
        final int lSize = (mask != null) ? mask.length() : size;
        if (lSize == 0)
        {
            writer.startElement("em", null);
            writer.writeText("Pls specify a size or a mask attribute !!!", null);
            writer.endElement("em");
        }
        else
        {
            final int lBlocks = (mask != null) ? mask.length() - placeholders(mask) : size;

            if (clientIds.size() == 0)
                for (int i = 0; i < lBlocks; i++)
                {
                    clientIds.add(generateClientId(context, i));
                }

            for (int i = 0, j = 0; i < lSize; i++)
            {
                writer.startElement("input", null);
                writer.writeAttribute("type", "text", null);
                writer.writeAttribute("maxLength", 1, null);
                if (!isPlaceholder(mask, i))
                {
                    lClientId = clientIds.get(j);
                    writer.writeAttribute("name", lClientId, null);
                    writer.writeAttribute("id", lClientId, null);
                    writer.writeAttribute("pos", String.valueOf(j), null);
                    writer.writeAttribute("prev", (j > 0) ? clientIds.get(j - 1) : null, null);
                    writer.writeAttribute("next", (j < lBlocks-1) ? clientIds.get(j+1) : null, null);
                    writer.writeAttribute("size", lBlocks, null);
                    writer.writeAttribute("filter", filter, null);
                    writer.writeAttribute("callback", callback, null);
                    writer.writeAttribute("cellCallback", cellCallback, null);
                    final String lKeyHandler = "pm$bi.handleKeys(event, this)";
                    writer.writeAttribute("onkeyup", lKeyHandler, null);
                    writer.writeAttribute("onkeypress", lKeyHandler, null);
                    writer.writeAttribute("onkeydown", lKeyHandler, null);
                    writer.writeAttribute("onclick", "this.select();", null);
                    if (lValue != null && lValue.length()>j)
                    {
                        final String lChar = lValue.substring(j, j + 1);
                        if (!" ".equals(lChar))
                            writer.writeAttribute("value", lChar, "value");
                    }
                    writer.writeAttribute("style", "width:1.4em; text-align:center", null);
                    writer.writeAttribute("autocomplete", "off", null);
                    j++;
                }
                else
                {
                    writer.writeAttribute("value", mask.substring(i, i + 1), null);
                    writer.writeAttribute("style", "width:1em; border:none; text-align:center; background-color:transparent;", "style");
                    writer.writeAttribute("readonly", "", "readonly");
                    writer.writeAttribute("disabled", "", "disabled");
                }

                writer.endElement("input");
                writer.write("\n");

            }
        }
    }

    private boolean isPlaceholder(String mask, int pos)
    {
        return mask != null && !("X".equalsIgnoreCase(mask.substring(pos, pos + 1)));
    }

    private int placeholders(String mask)
    {
        int lCnt = 0;
        for (int i = 0; mask != null && i < mask.length(); i++)
            if (!("X".equalsIgnoreCase(mask.substring(i, i + 1))))
                lCnt++;
        return lCnt;
    }

    public Object saveState(FacesContext context)
    {
        if (values == null)
            values = new Object[7];

        values[0] = super.saveState(context);
        values[1] = size;
        values[2] = clientIds;
        values[3] = mask;
        values[4] = filter;
        values[5] = callback;
        values[6] = cellCallback;
        return values;
    }

    public void restoreState(FacesContext context, Object state)
    {
        values = (Object[]) state;
        super.restoreState(context, values[0]);
        size = (Integer) values[1];
        clientIds = (List) values[2];
        mask = (String) values[3];
        filter = (String) values[4];
        callback = (String) values[5];
        cellCallback = (String) values[6];
    }

    private String generateClientId(FacesContext context, int i)
    {
        if (getId() != null)
            return getId() + "_" + i;
        else
        {
            final String lParentId = getFormId(context);
            final String lId = context.getViewRoot().createUniqueId();
            return new StringBuilder().append(lParentId).append(NamingContainer.SEPARATOR_CHAR).append(lId).toString();
        }
    }

    private String getFormId(FacesContext context)
    {
        UIComponent lParent = this.getParent();
        final String lParentId;
        while (lParent != null && !(lParent instanceof NamingContainer))
            lParent = lParent.getParent();
        if (lParent != null)
        {
            lParentId = lParent.getClientId(context);
        }
        else
        {
            //TODO
            throw new IllegalStateException("No naming container found");
        }
        return lParentId;
    }

    public void decode(FacesContext context)
    {
        super.decode(context);
        StringBuffer lBuf = new StringBuffer();
        for (String lId : clientIds)
        {
            final String lVal = context.getExternalContext().getRequestParameterMap().get(lId);
            if (lVal != null)
                if ("".equals(lVal))
                    lBuf.append(" ");
                else
                    lBuf.append(lVal);
            else
                //TODO
                throw new IllegalStateException("Missing request input values");
        }
        setSubmittedValue(lBuf.toString().trim());
    }

    public void setSize(int size)
    {
        this.size = size;
    }

    public String getRendererType()
    {
        return null;
    }

    public String getMask()
    {
        return mask;
    }

    public void setMask(String mask)
    {
        this.mask = mask;
    }

    public String getFilter()
    {
        return filter;
    }

    public void setFilter(String filter)
    {
        this.filter = filter;
    }

    public String getCallback()
    {
        return callback;
    }

    public void setCallback(String callback)
    {
        this.callback = callback;
    }

    public void setCellCallback(String cellCallback)
    {
        this.cellCallback = cellCallback;
    }

}
